/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.composite.helper;

import java.util.Iterator;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.uml2.uml.AggregationKind;
import org.eclipse.uml2.uml.Component;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.EncapsulatedClassifier;
import org.eclipse.uml2.uml.Port;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.UMLFactory;

import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.composite.Activator;
import com.cea.papyrus.diagram.composite.preference.CompositeDiagramPreferenceConstants;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * Factory that can create and delete a {@link org.eclipse.uml2.uml.Port} in the context of
 * a composite diagram.
 */
public class NodePortHelper extends NodeHelper {

	///////////////////////////////////////////////////////////////////////////
	// GRAPHICAL MANAGEMENT 
	///////////////////////////////////////////////////////////////////////////

	/**
	 * The default container. 
	 */
	private GraphElement defaultContainer;

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.INodeFactory#createGraphNode()
	 */
	/**
	 * 
	 * 
	 * @param umlChild 
	 * @param size 
	 * @param location 
	 * @param parent 
	 * 
	 * @return 
	 */
	@Override
	public GraphNode createGraphElement(Element umlChild, GraphElement parent, Point location, Dimension size){
		defaultContainer = parent.getContainer();
		// graph element must be added to the parent graph element (specific to port)
		GraphNode node = super.createGraphElement(umlChild, parent.getContainer(), location, size);
		// all caluclus are made in absolute positions
		Point newpos= new Point(0,0);
		newpos=LookForElement.calculateAbsolutePosition((GraphNode)parent, location);
		
		//to say that the port belogn to entity parent
		node.setSemanticParent(parent);
		// set defaut size, if null
		node.setSize(new Dimension(20, 20));

		// set correct position: calculate position for the port
		node.setPosition(inRunner((GraphNode)parent, newpos, node.getSize()));
		Point origin=LookForElement.calculateAbsolutePosition(((GraphNode)parent.getContainer()),new Point(0,0));
		node.setPosition(node.getPosition().getTranslated(origin.getNegated().getCopy()));
		
		//create label
		NodeHelper nh= new NodeHelper();
		GraphElement ge= nh.createGraphElement(null, null, new Point(node.getPosition().x+15, node.getPosition().y), null);
		ge.setSemanticParent(node);
		ge.setContainer(parent.getContainer());


		return node;
	}

	/**
	 * 
	 * 
	 * @param child 
	 * @param parent 
	 * 
	 * @return 
	 */
	public boolean removeGraphElement(GraphElement child, GraphElement parent){
		// Save the default container for undo command...
		defaultContainer = child.getContainer();
		child.setSemanticParent(null);
		child.eAdapters().clear();
		if (child.getContainer() != null) {
			child.getContainer().getContained().remove(child);
		}
		return true;
	}
	
	/**
	 * 
	 * 
	 * @param child 
	 * @param parent 
	 * 
	 * @return 
	 */
	public boolean undoCreateGraphElement(GraphElement child, GraphElement parent){
		child.setSemanticParent(null);
		child.eAdapters().clear();
		Iterator<DiagramElement> itchild=child.getSemanticChild().iterator();
		for(int i=0; i<child.getSemanticChild().size();){
			GraphElement subchild=(GraphElement)child.getSemanticChild().get(i);
			subchild.eAdapters().clear();
			subchild.setSemanticParent(null);
			subchild.setContainer(null);
		}
		
		 child.setContainer(null);
		 return true;
	}

	/**
	 * 
	 * 
	 * @param child 
	 * @param parent 
	 * 
	 * @return 
	 */
	public GraphElement addGraphElement(GraphElement child, GraphElement parent) {
		child.setSemanticParent(parent);
		if (child.getSemanticParent().getContainer() != null) {
			child.getSemanticParent().getContainer().getContained().add(child);
		} else {
			defaultContainer.getContained().add(child);
		}
		return child;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#canCreateGraphElement(com.cea.papyrus.diagraminterchange2.di2.GraphElement)
	 */
	/**
	 * 
	 * 
	 * @param parent 
	 * 
	 * @return 
	 */
	@Override
	public boolean canCreateGraphElement(GraphElement parent) {
		if(parent instanceof Diagram) {
			return false; //this a diagram
		} else if (parent.getSemanticModel()!=null){
			Element parentElt= ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
			if(parentElt!=null){
				if (parentElt instanceof org.eclipse.uml2.uml.Class ){
					return true;
				}
				else if (parentElt instanceof org.eclipse.uml2.uml.Property ){
					if(((Property)parentElt).getType() instanceof org.eclipse.uml2.uml.Class){
						return true;
					}
				}
			}
		}
		return false;
	}


	///////////////////////////////////////////////////////////////////////////
	// UML MANAGEMENT 
	///////////////////////////////////////////////////////////////////////////

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#createUMLElement(java.lang.String, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param umlParent 
	 * @param name 
	 * 
	 * @return 
	 */
	@Override
	public  org.eclipse.uml2.uml.Element createUMLElement(String name,  org.eclipse.uml2.uml.Element umlParent){
		Debug.debug(this,"Call createPort()", channel);
		EncapsulatedClassifier portContainer=null;
		if (umlParent instanceof Property){
			portContainer= (EncapsulatedClassifier)((Property) umlParent).getType();
		}
		// Added by A. Lanusse to allow port creation on a component
		else if (umlParent instanceof Component){
				portContainer=((Component) umlParent);
			}
		else{
			//class
			portContainer=(EncapsulatedClassifier)umlParent;
		}
		org.eclipse.uml2.uml.Port aport=UMLFactory.eINSTANCE.createPort();
		aport.setName(name);
		aport.setAggregation(AggregationKind.COMPOSITE_LITERAL);
		portContainer.getOwnedPorts().add(aport);
		return aport;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#canExecuteCreateElement(org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param umlParent 
	 * 
	 * @return 
	 */
	@Override
	public boolean canCreateUMLElement(Element umlParent) {
		// Added by A. Lanusse Test if umlParent is a Component
		if(umlParent instanceof org.eclipse.uml2.uml.EncapsulatedClassifier||umlParent instanceof Property || umlParent instanceof Component) {
			return true;
		}
		
		return false;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#addElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 */
	@Override
	public boolean addUMLElement(Element element, Element umlParent) {
		// Added by A. Lanusse allow add port to Component
		// Useless since Component is an EncapsulatedClassifier
		if(umlParent instanceof org.eclipse.uml2.uml.EncapsulatedClassifier) {
			if(((EncapsulatedClassifier)umlParent).getOwnedPorts().contains(element)){
				return true;
			}
			return ((EncapsulatedClassifier)umlParent).getOwnedPorts().add((Port)element);
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#removeElement(org.eclipse.uml2.uml.Element, org.eclipse.uml2.uml.Element)
	 */
	/**
	 * 
	 * 
	 * @param element 
	 * @param umlParent 
	 * 
	 * @return 
	 */
	@Override
	public boolean removeUMLElement(Element element, Element umlParent) {
		// Added by A. Lanusse allow add port to Component
		// Useless since Component is an EncapsulatedClassifier
		if(umlParent instanceof org.eclipse.uml2.uml.EncapsulatedClassifier) {
			return ((EncapsulatedClassifier)umlParent).getOwnedPorts().remove(element);
		}
		return false;
	}

	/* (non-Javadoc)
	 * @see com.cea.papyrus.common.factories.NodeFactory#getBaseString()
	 */
	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	public String getBaseString() {
		return "Port_";
	}

	/**
	 * Generic method that returns a new unique name within a namespace.
	 * 
	 * @param umlParent the parent of the element to create
	 * 
	 * @return a distinguisable name within the namespace of the umlParent
	 */
	public String getNewUMLElementName(Element umlParent) {
		if (umlParent instanceof Property){
			return this.getNewUMLElementName((EncapsulatedClassifier)((Property) umlParent).getType());
		}
		String name = ""; //$NON-NLS-1$

		// i <10000: avoid infinite loops
		for(int i=0; i< 10001; i++ ) {
			name = getBaseString()+i;
			if(null == ((EncapsulatedClassifier)umlParent).getOwnedPort(name,null)) {
				return name;
			}
		}
		return getBaseString()+"X"; //$NON-NLS-1$
	}

//	/*
//	* (non-Javadoc)
//	* @see com.cea.papyrus.common.factories.NodeFactory#createGraphNode()
//	*/
//	@Override
//	public GraphNode createGraphElement() {

//	//create Package
//	org.eclipse.uml2.uml.Port aport=UMLFactory.eINSTANCE.createPort();
//	aport.setName("Port_"+number);
//	aport.setAggregation(AggregationKind.COMPOSITE_LITERAL);
//	GraphNode graphNode=super.createGraphNodeWithSemantic(aport);

//	// Set colors and font
//	setPreferences(graphNode);

//	graphNode.setSize(new Dimension(20,20)); // dimension fo a package
//	graphNode.getContained().add(super.createGraphElement());//Create graphNode for the name
//	graphNode.getContained().add(super.createGraphElement());//Create graphNode for the stereotype
//	number++;		
//	return graphNode;
//	}

//	/*
//	* (non-Javadoc)
//	* @see com.cea.papyrus.common.factories.NodeFactory#createGraphNode(org.eclipse.uml2.uml.NamedElement, com.cea.papyrus.diagraminterchange2.di2.Diagram)
//	*/
//	@Override
//	public ArrayList createGraphNode(Element umlElement, Diagram diagram) {

//	Debug.debug(this,"Call NodePortFactory.createGraphNode()", channel);

//	//look for graphNode of the umlElement parent
//	//the parent graphnode may be associated to a class or a property that is typed with the clas that contains the property.

//	//1.  look foe all graphNode that will contained the new port graphNode

//	//1.a. look for class if the parent is representation of a class
//	ArrayList parentGraphNode= LookForElement.lookForGraphElement(umlElement.getOwner(), diagram, new ArrayList());

//	//1.b. look for property if the parent is a representation of a part
//	Model umlModel=((Port)umlElement).getModel();

//	//look for all properties of the model
//	ArrayList propertiesList= new com.cea.utils.Package(umlModel).getAllProperties(new ArrayList());

//	Iterator<Property> it =propertiesList.iterator();
//	while (it.hasNext()){
//	Property aProperty=it.next();
//	// the type is the same as the container of the port
//	if ((aProperty.getType()!=null) && aProperty.getType().equals(umlElement.getOwner())){
//	//look for corresponded graphNode
//	parentGraphNode= LookForElement.lookForGraphElement(aProperty, diagram, parentGraphNode);

//	}

//	}
//	//2. for each parent GraphNode add a new GraphNode to represent the port
//	ArrayList createdNode =new ArrayList();
//	Iterator itGraphNode =parentGraphNode.iterator();

//	if (parentGraphNode.size()!=0){
//	while(itGraphNode.hasNext()){
//	//current node
//	GraphNode currentNode=(GraphNode)itGraphNode.next();
//	//node creation
//	GraphNode graphNode= this.createGraphElement();
////	createUMLbridgee
//	Uml1SemanticModelBridge semantic= Di2Factory.eINSTANCE.createUml1SemanticModelBridge();
//	semantic.setElement(umlElement);
//	graphNode.setSemanticModel(semantic);

//	//set position of the port
//	NodePortFactory np= new NodePortFactory();
//	graphNode.setPosition(np.inRunner(currentNode, graphNode.getPosition(), graphNode.getSize()));
//	//set semantic parent of the graphNode
//	graphNode.setSemanticParent(currentNode);
//	// add to the greatfather.
//	currentNode.getContainer().getContained().add(graphNode);

//	//add node to represent the label node
//	NodeFactory nf= new NodeFactory();
//	GraphNode graphNodeLabel= nf.createGraphElement();
//	graphNodeLabel.setSemanticParent(graphNode);
//	Point position=graphNode.getPosition().getCopy();
//	position.x=position.x+graphNode.getSize().width+10;
//	graphNodeLabel.setPosition(position);
//	currentNode.getContainer().getContained().add(graphNodeLabel);
//	createdNode.add(graphNode);

//	}

//	}

//	return createdNode;
//	}

//	/*
//	* (non-Javadoc)
//	* @see com.cea.papyrus.common.factories.NodeFactory#canExecuteCreateCommand(com.cea.papyrus.diagraminterchange2.di2.GraphNode)
//	*/
//	@Override
//	public boolean canCreateGraphElement(GraphNode parent) {

//	if(parent instanceof Diagram) {
//	return false; //this a diagram
//	} else if (parent.getSemanticModel()!=null){
//	Element parentElt= ((Uml1SemanticModelBridge)parent.getSemanticModel()).getElement();
//	if(parentElt!=null){
//	if (parentElt instanceof org.eclipse.uml2.uml.Class ){
//	return true;
//	}
//	else if (parentElt instanceof org.eclipse.uml2.uml.Property ){
//	if(((Property)parentElt).getType() instanceof org.eclipse.uml2.uml.Class){
//	return true;}
//	/*else if (((Property)parentElt).getType()==null){
//	PapyrusMessageDialog.display("In order to add a port, the property have to be typed", null);
//	return false;
//	}*/
//	}
//	}

//	}
//	return false;
//	}


	/**
 * this method calculate the good position of a port by taking account of his parent.
 * 
 * @param portDimension the dimension of the port
 * @param parentGP graphNode the virtual graphNode that contains the port
 * @param point the absolute position
 * 
 * @return the new position of the port in absolute coordinate
 */
	public Point inRunner(GraphNode parentGP,Point point, Dimension portDimension){
		int x= point.x;
		int y= point.y;
		Point selected =point.getCopy();
		//calculate absolute position of GP
		Point positionGP=LookForElement.calculateAbsolutePosition(parentGP,new Point(0,0));


		// position is constraint in the parent rectangle
		if (x<positionGP.x-portDimension.width/2){
			x=positionGP.x-portDimension.width/2;
		}
		if (x>positionGP.x+parentGP.getSize().width-portDimension.width/2){
			x=positionGP.x+parentGP.getSize().width-portDimension.width/2;
		}

		if (y<positionGP.y-portDimension.height/2){
			y=positionGP.y-portDimension.height/2;
		}
		if (y>positionGP.y+parentGP.getSize().height-portDimension.height/2){
			y=positionGP.y+parentGP.getSize().height-portDimension.height/2;
		}

		//calculte all 4 possible positions.		
		Point pointWEST= new Point(positionGP.x-portDimension.width/2, y);
		Point pointNORTH= new Point(x,positionGP.y-portDimension.height/2);
		Point pointEAST= new Point(positionGP.x+parentGP.getSize().width-portDimension.width/2, y);
		Point pointSOUTH= new Point(x,positionGP.y+parentGP.getSize().height-portDimension.height/2);

		// calculate all distance
		double distanceWEST=pointWEST.getDistance(point);
		double distanceNORTH=pointNORTH.getDistance(point);
		double distanceEAST=pointEAST.getDistance(point);
		double distanceSOUTH=pointSOUTH.getDistance(point);
		Debug.debug(this,"pointWEST: "+pointWEST+" "+distanceWEST,channel);
		Debug.debug(this,"pointNORTH: "+pointNORTH+" "+distanceNORTH,channel);
		Debug.debug(this,"pointEAST: "+pointEAST+" "+distanceEAST,channel);
		Debug.debug(this,"pointSOUTH: "+pointSOUTH+" "+distanceSOUTH,channel);

		//choose the point the minimum distance
		selected=pointWEST;
		double selecteddistance=distanceWEST;
		if (selecteddistance>distanceNORTH){
			selected=pointNORTH;
			selecteddistance=distanceNORTH;}

		if (selecteddistance>distanceEAST){
			selected=pointEAST;
			selecteddistance=distanceEAST;}

		if (selecteddistance>distanceSOUTH){
			selected=pointSOUTH;
			selecteddistance=distanceSOUTH;}
		Debug.debug(this,"miminmum distance: "+selected+" "+selecteddistance,channel);
		
		
		return selected;
	}

	/**
	 * Set graphnode colors, font and appearance.
	 * 
	 * @param graphNode the graph node
	 */
	private void setPreferences(GraphNode graphNode) {	
		// Get the preference store
		IPreferenceStore store = Activator.getDefault().getPreferenceStore();

		// Set colors and font
		graphNode.setBackgroundColor(CompositeDiagramPreferenceConstants.getPreferencePortBackgroundColor(store));
		graphNode.setBorderColor(CompositeDiagramPreferenceConstants.getPreferencePortBorderColor(store));
		graphNode.setFontColor(CompositeDiagramPreferenceConstants.getPreferencePortFontColor(store));
		FontData[] fontdata = CompositeDiagramPreferenceConstants.getPreferencePortFont(store);
		if (fontdata.length>0){
			graphNode.setFontFamily(fontdata[0].getName());
			graphNode.setFontSize(fontdata[0].getHeight());
		}
	}

}